#ifndef __M_BINDING_H__
#define __M_BINDING_H__

#include "../mqcommon/mq_helper.hpp"
#include "../mqcommon/mq_logger.hpp"
#include "../mqcommon/mq_msg.pb.h"
#include <iostream>
#include <unordered_map>
#include <memory>
#include <mutex>
#include <string>

namespace wsmq
{
    struct Binding
    {
        using ptr = std::shared_ptr<Binding>;
        std::string _binding_key;
        std::string _exchange_name;
        std::string _queue_name;
        Binding(){}
        Binding(const std::string& exchange_name, const std::string& queue_name, const std::string& binding_key):
                _binding_key(binding_key), _exchange_name(exchange_name),
                _queue_name(queue_name)
        {}
    };
    using MsgQueueBindingMap = std::unordered_map<std::string, Binding::ptr>; // 队列名与绑定信息的映射
    // 交换机名与队列绑定信息的映射，包含了所有绑定信息，并以交换机为单位进行区分
    using BindingMap = std::unordered_map<std::string, MsgQueueBindingMap>; 
    class BindingMapper
    {
    public:
        using ptr = std::shared_ptr<BindingMapper>;
        BindingMapper(const std::string& dbfile): _sql_helper(dbfile)
        {
            std::string path = FileHelper::parentDirectory(dbfile);
            FileHelper::createDirectory(path);
            assert(_sql_helper.open());
            createTable();
        }
        void createTable()
        {
            std::stringstream sql;
                sql << "create table if not exists binding_table(";
                sql << "exchange_name varchar(32), ";
                sql << "msgqueue_name varchar(32), ";
                sql << "binding_key varchar(128));";
                assert(_sql_helper.exec(sql.str(), nullptr, nullptr));
        }
        void removeTable()
        {
            std::string sql = "drop table if exists binding_table;";
                _sql_helper.exec(sql, nullptr, nullptr);
        }
        bool insert(Binding::ptr& binding)
        {
            std::stringstream sql;
            sql << "insert into binding_table values(";
            sql << "'" << binding->_exchange_name << "', ";
            sql << "'" << binding->_queue_name << "', ";
            sql << "'" << binding->_binding_key << "');";
            return _sql_helper.exec(sql.str(), nullptr, nullptr);
        }
        void remove(const std::string& ename, const std::string& qname)
        {
            std::stringstream sql;
                sql << "delete from binding_table where ";
                sql << "exchange_name='" << ename << "' and ";
                sql << "msgqueue_name='" << qname << "';";
                _sql_helper.exec(sql.str(), nullptr, nullptr);
        }
        void removeExchangeBinding(const std::string& ename)
        {
            std::stringstream sql;
                sql << "delete from binding_table where ";
                sql << "exchange_name='" << ename << "';";
                _sql_helper.exec(sql.str(), nullptr, nullptr);
        }
        void removeQueueBinding(const std::string& qname)
        {
            std::stringstream sql;
                sql << "delete from binding_table where ";
                sql << "msgqueue_name='" << qname << "';";
                _sql_helper.exec(sql.str(), nullptr, nullptr);
        }
        BindingMap recovery()
        {
            BindingMap result;
            // select exchange_name, msgqueue_name, binding_key from binding_table;
            std::string sql = "select exchange_name, msgqueue_name, binding_key from binding_table;";
            _sql_helper.exec(sql, selectCallback, &result);
            return result;
        }
    private:
        static int selectCallback(void* arg, int colnum, char** row, char** fields)
        {
            BindingMap* result = (BindingMap*)arg;
            Binding::ptr bp = std::make_shared<Binding>(row[0], row[1], row[2]);
            MsgQueueBindingMap& qmap = (*result)[bp->_exchange_name];
            qmap.insert(std::make_pair(bp->_queue_name, bp));
            return 0;
        }
        SqliteHelper _sql_helper;
    };
    class BindingManager
    {
    public:
        using ptr = std::shared_ptr<BindingManager>;
        BindingManager(const std::string& dbfile): _mapper(dbfile)
        {
            _bindings = _mapper.recovery();
        }
        bool bind(const std::string& ename, const std::string& qname, 
            const std::string& key, bool durable) // 由外界决定是否持久化绑定信息
            // 当队列和交换机都要持久化时，在持久化
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _bindings.find(ename);
            if(it != _bindings.end() && it->second.find(qname) != it->second.end())
            {
                return true;
            }
            Binding::ptr bp = std::make_shared<Binding>(ename, qname, key);
            if(durable == true)
            {   
                bool ret = _mapper.insert(bp);
                if(ret == false) return false;
            }
            MsgQueueBindingMap& qmap = _bindings[ename];
            qmap.insert(std::make_pair(qname, bp));
            return true;
        }
        void unbind(const std::string& ename, const std::string& qname)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto eit = _bindings.find(ename);
            if(eit == _bindings.end()) return ;
            auto qit = eit->second.find(qname);
            if(qit == eit->second.end()) return ;
            _mapper.remove(ename, qname);
            _bindings[ename].erase(qname);                                         
        }
        void removeExchangeBind(const std::string& ename)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _mapper.removeExchangeBinding(ename);
            _bindings.erase(ename);
        }
        void removeQueueBind(const std::string& qname)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _mapper.removeQueueBinding(qname);
            for(auto start = _bindings.begin(); start != _bindings.end(); start++)
            {
                start->second.erase(qname);
            }
        }
        
        MsgQueueBindingMap getExchangeBindings(const std::string& ename)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto eit = _bindings.find(ename);
            if(eit == _bindings.end()) return MsgQueueBindingMap();
            return eit->second;
        }
        Binding::ptr getBinding(const std::string& ename, const std::string& qname)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto eit = _bindings.find(ename);
            if(eit == _bindings.end()) return Binding::ptr();
            auto qit = eit->second.find(qname);
            if(qit == eit->second.end()) return Binding::ptr();
            return qit->second;
        }
        bool exists(const std::string& ename, const std::string& qname)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto eit = _bindings.find(ename);
            if(eit == _bindings.end()) return false;
            auto qit = eit->second.find(qname);
            if(qit == eit->second.end()) return false;
            return true;
        }

        size_t size()
        {
            size_t total_size = 0;
            std::unique_lock<std::mutex> lock(_mutex);
            for(auto start = _bindings.begin(); start != _bindings.end(); start++)
            {
                total_size += start->second.size();
            }
            return total_size;
        }
        void clear()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _mapper.removeTable();
            _bindings.clear();
        }
    private:
        std::mutex _mutex;
        BindingMapper _mapper;
        BindingMap _bindings;
    };
}
#endif